﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//指针的进阶
// 
//1.字符指针
//int main()
//{
//	char ch = 'w';
//	char* pc = &ch;
//	*pc = 'w';
//
//	return 0;
//}
//还有一种使用方式
//int main()
//{
//	const char* pstr = "hello world";//这里是把一个常量字符串的首字符‘h’的地址放在pstr指针变量里面，通过pstr就可以访问这个字符串
//	*pstr = 'y';
//	printf("%s\n", pstr);//字符串打印只要提供首字符的地址就可以打印整个字符串
//	return 0;
//}//常量字符串里面的字符是不可以被改变的，在字符指针的前面加上const，如果常量字符串被改变就会报警告

//一道题
//int main()
//{
//	char str1[] = "hello world";
//	char str2[] = "hello world";
//	const char* str3 = "hello world";
//	const char* str4 = "hello world";
//
//	if (str1 == str2)
//		printf("str1 and str2 are same\n");//x
//	else
//		printf("str1 and str2 are not same\n");//对
//	if(str3==str4)
//		printf("str3 and str4 are same\n");//对
//	else
//		printf("str3 and str4 are not same\n");//x
//	return 0;
//}
//这⾥str3和str4指向的是⼀个同⼀个常量字符串。C / C++会把常量字符串存储到单独的⼀个内存区域，
//当⼏个指针指向同⼀个字符串的时候，他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始
//化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同，str3和str4相同。

//2.指针数组
//整型数组 - 存放整形的数组
//字符数组 - 存放字符的数组
//指针数组 - 存放指针（地址）的数组

//int main()
//{
//	//存放字符指针的数组
//	const char* arr[4] = { "abcdef","qwer","hello","hehe" };
//	int i = 0;
//	for (i = 0; i < 4; i++)
//	{
//		printf("%s\n", arr[i]);
//	}
//	return 0;
//}

//int main()
//{
//	int arr1[5] = { 1,2,3,4,5 };
//	int arr2[5] = { 2,3,4,5,6 };
//	int arr3[5] = { 3,4,5,6,7 };
//	int arr4[5] = { 0,0,0,0,0 };
//
//	int* arr[4] = { arr1,arr2,arr3,arr4 };
//
//	int i = 0;
//	int j = 0;
//	for (i = 0; i < 4; i++)
//	{
//		for (j = 0; j < 5; j++)
//		{
//			//printf("%d ", arr[i][j]);
//			printf("%d ", *(arr[i] + j));
//		}
//		printf("\n");
//	}
//	return 0;
//}

//3.数组指针
//字符指针 - 存放字符地址的指针 - 指向字符的指针 char*
//整形指针 - 存放整形地址的指针 - 指向整型的指针 int*
//浮点型指针 - 指向浮点型的指针 float*  double*
//数组指针 - 存放数组地址的指针 - 指向数组的指针

//int main()
//{
//	char ch = 'w';
//	char* pc = &ch;//字符指针
//
//	int num = 10;
//	int* pi = &num;//整型指针
//
//	int arr[10];
//	int (*pa)[10] = &arr;//pa是一个数字指针
//
//	return 0;
//}

//3.1&数组名和数组名

//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	//&arr取出的是数组的地址，只有数组的地址才需要数组来接收
//	int (*p)[10] = &arr;
//	//不可以这样写：int (*p2)[10] = arr;因为arr是数组名，它的类型是int*,与&arr不一样,如果想要强转：int (*p2)[10] = (int (*)[10])arr;
//
//	//数组名 - 数组首元素的地址
//	//&数组名 - 是数组的地址
//	//数组首元素的地址和数组的地址从值的角度看是一样的，但是意义不一样
//
//	printf("%p\n", arr);//类型是int*
//	printf("%p\n", arr + 1);//跳过4个字节
//
//	printf("%p\n", &arr[0]);//类型是int*
//	printf("%p\n", &arr[0] + 1);//4
//
//	printf("%p\n", &arr);//类型是int(*)[10] - 数组指针类型
//	printf("%p\n", &arr + 1);//40
//
//	return 0;
//}

//3.2数组指针的使用
//数组指针指向的是数组，那数组指针中存放的应该是数组的地址

//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	/*int* p = arr;
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", *(p + i));
//	}*/
//
//	//使用数组指针实现
//	int (*p)[10] = &arr;//对数组的访问需要循环来完成，不能直接打印，因为没有像字符串那样有‘\0’作为结束标志
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", (*p)[i]);
//      //printf("%d ",arr[i]);  // *p = * &arr = arr
//	}
//	return 0;
//}

//void print1(int arr[3][4], int r, int c)
//{
//	int i = 0;
//	for (i = 0; i < r; i++)
//	{
//		int j = 0;
//		for (j = 0; j < c; j++)
//		{
//			printf("%d ", arr[i][j]);
//		}
//		printf("\n");
//	}
//}
//int main()
//{
//	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
//	print1(arr, 3, 4);
//	return 0;
//}

//void print2(int (*p)[4], int r, int c)//将二维数组首元素的地址，即第一行的地址传给p，则p指向第一行的元素，p+1就跳过4个元素，即指向第二行
//{
//	int i = 0;
//	for (i = 0; i < r; i++)
//	{
//		int j = 0;
//		for (j = 0; j < c; j++)
//		{
//			printf("%d ", (*(p + i))[j]);//（）优先级高于[ ]
//			//printf("%d ", p[i][j]);//*(p+i) == p[i]
//		}
//		printf("\n");
//	}
//}
//int main()
//{
//	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };//二维数组，数组名是首元素的地址，则二维数组首元素地址是第一行的地址（int [4]）,
//	//print1(arr, 3, 4);                             //相当于一维数组的地址，将一维数组的地址放到一个数组指针中去，即int (*p)[4]
//	print2(arr, 3, 4);
//	return 0;
//}

//4.数组参数、指针参数
//4.1一维数组传参

//void test(int arr[])//ok 
//{}
//void test(int arr[10])//ok 
//{}
//void test(int* arr)//ok 
//{}
//void test2(int* arr[])//ok 
//{}
//void test2(int* arr[20])//ok 
//{}
//void test2(int** arr)//ok 
//{}
//int main()
//{
//	int arr[10] = { 0 };
//	int* arr2[20] = { 0 };
//	test(arr);//数组名是首元素的地址，而arr每个元素的类型是int，int的地址是int*
//	test2(arr2);//数组名表示首元素的地址，而arr2每个元素的类型是int*,是一级指针；int*的地址，就是int**（二级指针）
//	return 0;
//}

//4.2二维数组传参

//void test(int arr[3][5])//ok  
//{}
//void test(int arr[][])//error
//{}
//void test(int arr[][5])//ok
//{}
////对于一个二维数组，可以不知道多少行，但必须知道多少列，即行数可以省略，列数不可以省略
//void test(int* arr)//整型指针 error
//{}
//void test(int* arr[5])//指针数组 error   
//{}
//void test(int (*arr)[5])//ok
//{}
//void test(int** arr)//要接收的是数组的地址，而二级指针是接收一级指针的地址的  error
//{}
//int main()
//{
//	int arr[3][5] = { 0 };
//	test(arr);//二维数组数组名是首元素的地址，即第一行的地址（一维数组的地址）
//	return 0;
//}

//4.3一级指针传参

//void print(int* p, int sz)
//{
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", *(p + i));
//	}
//}
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = arr;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	//一级指针p，传给函数
//	print(p, sz);
//	return 0;
//}
////思考：当一个函数的参数部分为一级指针的时候，函数能接收什么参数？
//void test(int* p)
//{}
//int main()
//{
//	int a = 10;
//	int* p = &a;
//	int arr[10];
//
//	test(&a);
//	test(p);
//	test(arr);
//	return 0;
//}
//函数可以接收的参数：变量的地址、一级指针变量、一维数组的数组名

//4.4二级指针传参

//void test(int** ptr)
//{
//	printf("num = %d\n", **ptr);
//}
//int main()
//{
//	int n = 10;
//	int* p = &n;
//	int** pp = &p;
//	test(pp);
//	test(&p);
//
//	return 0;
//}
////思考：当一个函数的参数部分为二级指针的时候，函数能接收什么参数？
//void test(int** p)
//{}
//int main()
//{
//	int** ptr;
//	int* pp;
//	int* arr[10];
//
//	test(ptr);
//	test(&pp);
//	test(arr);
//	return 0;
//}
//函数可以接收的参数：二级指针变量、一级指针变量的地址、指针数组的数组名

//5.函数指针
//数组指针 - 指向数组的指针
//函数指针 - 指向函数的指针

//int Add(int x, int y)
//{
//	return x + y;
//}
//int main()
//{
//	//printf("%p\n", &Add);
//	/*int arr[10];
//	int (*p)[10] = &arr;*/
//
//	int (*pf)(int, int) = &Add;
//	int ret = (*pf)(2, 3);
//	//int ret = pf(2, 3);//(*pf)中，*可以省略，或者无论加多少颗*都可以，结果都一样（*****p）
//	//int ret = Add(2, 3);
//	printf("%d\n", ret);
//
//	//&函数名和函数名都是函数的地址
//	//int (*pf)(int, int) = Add;
//
//	//pf是一个存放函数地址的指针变量 - 函数指针
//	return 0;
//}

//两段有趣的代码
int main()
{
	( *( void (*)() )0 )();
	//该代码是一次函数调用
	//调用0地址处的一个函数
	//首先代码中将0强制转化为类型为 void (*)() 的函数指针
	//然后去调用0地址处的函数

	void ( *signal( int, void(*)(int) ) )(int);
	//该代码是一次函数的声明
	//声明的函数叫signal
	//signal函数的参数有2个，第一个是int类型，第二个是函数指针类型void (*)(int)，该函数指针能够指向的那个函数的参数是int，返回类型是void
	//signal函数的返回类型是一个函数指针void (*)(int)，该函数指针能够指向的那个函数的参数是int，返回类型是void
	//void (*)(int) signal( int, void(*)(int)


	//可以对这个代码进行简化 - 使用关键字typedef:typedef是⽤来类型重命名的，可以将复杂的类型，简单化。
	//例如：
	typedef unsigned int uint;//将unsigned int类型重命名为uint

	//函数指针类型重命名的新的类型名必须在*的右边

	typedef void (*pf_t)(int);//将函数指针类型void (*)(int)重命名为pf_t
	pf_t signal(int, pf_t);
	return 0;
}